5 Flex の他の特徴


ここでは、Lex が提供していない機能や一般にはあまり使われない機能を説明します。Flex はほぼ 100% Lex 互換ですが、Lex よりも後に実装されたため、性能的にもより優れており、また、広範な用途に使えるスキャナをより簡単に作成することができるよう特別な機能を提供しています。

5.1 大文字・小文字を区別しないスキャナ

多くの言語は、その識別子において大文字・小文字を区別しません(Pascal、BASIC、FORTRAN 等)。Lex にも、大文字・小文字を区別しないスキャナを指定するための方法がありますが、それらは概して美しくなく、理解するのも困難です。個々の文字を置き換えてくれる定義を長いリストにして作成することも可能ですし、すべての識別子を受け付ける1つのルールを作成し、そのルールにおいて大文字・小文字を変換してからトークンの種類を返すようにすることも可能です。以下のコードは、これら2つの方法を示すものです。定義を使うのであれば、以下のようになります。

これに似た操作をサブルーチンで実行するのであれば、以下のようにします。 もっともこれは、関数呼び出しの必要があるため、より非効率です(Flex ではパターンの複雑さは大した影響をもたらしません)。

ほかにもこれと同じことをおこなう方法がありますが、いずれもエレガントではありません。

5.1.1 -i オプション

Flex はこの問題を簡単に解決するための方法を提供しています。コマンドラインで -i オプションを使うことによって、入力情報の大文字・小文字を区別しないスキャナを生成するよう Flex に対して通知することができます。これは、Flex では上記のようなテクニックを使う必要がないということを意味しています。例えば、

は、-i オプションを使うことによって、BEGIN、begin、BeGiN、およびこれ以外のすべての大文字・小文字の組み合わせにマッチします。これは、Lex において同様のことを行うための方法よりもずっと簡単です。

-i オプションには1つ注意すべき点があります。それは、スキャナが大文字・小文字を区別しないだけで、その変換まではしてくれないということです。このことは、Pascal においてシンボル名をハッシュしたいような場合、自分でシンボル名を大文字もしくは小文字に変換しなければならないことを意味しています。そうしないと、FOO と foo は異なるものとして扱われます。これは、シンボルを保存するルーチンの中で対処することもできますし、YY_USER_ACTION を使うことによって対処することもできます。これを実現する方法の例については、***ページの 4.1 節「Flex と C」における YY_USER_ACTION の説明を参照してください。

5.2 -I オプション:対話的なスキャナ

Flex の1つの問題に、どのルールを適用するかを決定する前に、入力情報中の次の1文字を先読みする必要があるということがあります。対話的ではない使い方をする場合にはこれは問題にはなりませんが、Flex を使ってユーザから直接入力文字を受け取るような場合には問題になることがあります。

このような場合を2つ挙げると、1つはシェルとやりとりする場合、もう1つはデータベースのフロント・エンドとやりとりする場合です。通常のアクションは、改行が入力の終わりを表わすというもので、改行自身は一種の「中身のない文」として受け付けるのが望ましいのですが、通常の Flex スキャナではこれは可能ではありません。Flex が常に先読みをするという事実は、改行が認識されるためにはユーザが次の行を入力しなければならないということを意味しています(すなわち、単一の改行は、それだけでは認識されず、他の文字が入力される必要があるということです)。これはシェル上ではまったく望ましくありません。

Flex にはこれを回避する方法があります。コマンドラインで -I オプションを使うと、Flex は必要な場合にしか先読みをしない特別な対話型スキャナを生成します。この種のスキャナはユーザから直接入力を受け取るのに適していますが、若干の性能劣化を引き起こすかもしれません。

注:-I オプションは、-f、-F、-Cf、-CF フラグと一緒に使うことはできません。このことは先読みができないことから来る性能劣化に加えて、パーサも性能向上のために最適化することができないということを意味しています。

-I オプションに関連するマイナス面は通常はきわめて小さいので、入力情報がどこから来るのか確かではなく、性能向上のための最適化を施す可能性を諦めても構わないのであれば、コマンドラインにおいて -I オプションを使う方が良いでしょう。

5.3 テーブルの圧縮とスキャナのスピード

テーブルの圧縮とスピードの領域では、Flex は Lex の能力をはるかに上回っています。Flex は、使われるオプションに応じて、Lex よりもはるかに高速なテーブル、あるいは、はるかに小さいテーブルを生成することができます。この節では、利用可能なオプションとそれらがスピードにもたらす影響について説明します。一般的には、テーブルが圧縮されるほど、そのスピードは遅くなります。Flex では、これらのオプションはコマンドラインで与えられます。オプションは以下のとおりです。
 

-f、-Cf  このオプションは Flex がフル・テーブル(full table)を生成すべきであることを指定します。このテーブルはまったく圧縮されず、サイズが大きくなりますが、スピードは速くなります。これらのオプションが指定された場合は、アクションの部分に REJECT を使うことはできない点に注意してください。 

注:-f フラグと -F フラグは、Flex が生成するテーブルにおいて相違をもたらします。-f フラグはフル・テーブル(full table)を生成し、-F フラグはファスト・テーブル(fast table)を生成します。ファスト・テーブルとは、スピードを最大限にするよう最適化されたテーブル形式であり、一方、フル・テーブルには最適化は一切施されません。もたらされる結果はよく似ていますが、テーブルのサイズは大きく異なるものになる可能性があります。 
 

-F、-CF  このオプションはファスト・テーブル(fast table)形式を用いてテーブルを生成するよう Flex に通知します。一般的には、このテーブルのスピードは先に説明したフル・テーブル(full table)とほとんど同じですが、使われるパターンに応じて、そのサイズはより小さくもより大きくもなる可能性があります。原則として、すべての識別子をキャッチするルールのほかにキーワードの一覧も持つファイルに対しては、-f オプションを使うべきです。例えば、 
    ALPHA           [a-zA-Z] 
    NUM              [0-9] 
    ALPHANUM    {ALPHA}|{NUM} 
    %% 
    begin                            return(BEGIN_SYM); 
    ... ルールとアクション ... 
    end                               return(END_SYM); 
    {ALPHA}{ALPHANUM}*     return(IDENTIFIER); 
は -f フラグを使って処理すべきであり、 
    {ALPHA}{ALPHANUM}*    {ECHO; 
                                        return(lookup(yytext));} 
は -F フラグを使って処理すべきです。これらのオプションが指定されている場合は、アクションの部分に REJECT を使うことができない点に注意してください。 
 
-Ce  このオプションを使うと、性能にはわずかしか影響を及ぼさずに、テーブルのサイズをかなり減少させることができます。-Ce が使われると、Flex は同等クラス(equivalence class)を作成します。同等クラスとは同一の方法で使われる文字のグループです。例えば、使われる数字が集合 [0-9] の範囲に限定されるのであれば、0 から 9 までの数は同等クラスの中に置かれることになります。 
 
-Cfe、-CFe  同等クラス(equivalence class)を持つファスト・テーブル(fast table)。このオプションによって生成されたスキャナもまた高速であり、かつ、-Cf あるいは -CF を指定して生成されたスキャナと比較してサイズもはるかに小さくなる可能性があります。サイズもしくはスピードの一方が他方に比べてはるかに重要であるということがないのであれば、これは良い組み合わせです。 
 
-Cm  これは Flex に対してメタ同等クラス(meta-equivalence class)を使うよう通知します。これは、一緒に使われることが多い文字の集合、もしくは(同等クラスが使われている場合には)同等クラスです。同等クラスが使われた場合は性能はさらに悪くなりますが、これは多くの場合、テーブル・サイズを小さくするのに非常に効果的な方法です。 
 
-Cem  デフォルトのテーブル圧縮。このオプションで生成されるスキャナは、Flex が生成するスキャナの中で事実上最も小さく、かつ、最も性能の劣るものになります。 
 
-C  -C オプション単体では、同等クラス(equivalence class)、メタ同等クラス(meta-equivalence class)を使わずにテーブルを圧縮するよう Flex に対して通知します。 
 
注:-Cxx オプションはコマンドライン上には1つだけ指定すべきです。というのは、これらのうち最後に見つかったオプションだけが実際の効果を持つからです。したがって、 は Flex に -Cem オプションを使わせることになります。

Flex のデフォルトの動作は、コマンドライン上で -Cem オプションを使った場合に相当します。これは圧縮を最大限におこなうことになり、一般的には最も遅いスキャナが生成されることになります。こうした小さなテーブルはより速く生成され、コンパイルもより速く実行されるので、このデフォルトは開発段階では非常に便利です。スキャナのデバッグが終了した後は、より高速な(そして通常はよりサイズの大きい)スキャナを作成することができます。

5.4 翻訳テーブル

翻訳テーブルは、文字をグループにマップするのに使われます。このテーブルは Lex の持つ機能の1つですが、POSIX では定義されていません。Flex でも翻訳テーブルを使うことはできますが、これはサポート対象外の機能です。Flex においては翻訳テーブルは不要です。というのは、Flex には -i オプションによる同等クラス(equivalence class)というものがあり、これが翻訳テーブルと同等の機能を実現しているからです(***ページの 5.1.1 節「-i オプション」を参照)。翻訳テーブルの機能は、互換性のためだけに存在する余分な機能です。翻訳テーブルを使うことはお勧めできません。翻訳テーブルを使いたいのであれば、それは定義ファイルの先頭の定義セクションにおいて定義されなければなりません。

翻訳テーブルの一般的な形式は以下のとおりです。

これは、A から Z までの任意の文字がルールの中で使われている場合、そのパターンは A から Z までのどの文字にもマッチするということを意味しています。したがって、A(BC) と X(YZ) はまったく同一であるということになります。

5.5 複数の入力バッファ

スキャナが複数のファイルからの入力を処理することができるということが必要になる状況はたくさんあります。例えば、多くの Pascal の実装ではコンパイル時に複数のファイルを取り込むことを許していますし、C ではスキャナもしくはプリプロセッサが #include 文を処理できなければなりません。このことが意味しているのは、スキャナは、カレントなスキャン処理のコンテキストを保存してから新しいコンテキストに変更し、その後に以前の状態と完全に一致する状態に復帰することができなければならないということです。

Flex スキャナは、スキャン処理のコンテキストを維持するために余分の処理が必要になるような、大きな入力バッファを使っています。しかし Flex は、複数の入力バッファの作成、切り替え、削除が非常に簡単におこなえるような特別な機能を提供しています。

5.5.1 バッファを操作する関数

Flex は、複数の入力バッファを取り扱うために、以下のような関数やマクロを提供しています。
 
 

YY_BUFFER_STATE yy_create_buffer(FILE *file, int size file で指定されるファイルのために、size で指定される数の文字を格納するのに十分な大きさのバッファを作成します。この関数は、後に複数のバッファ間の切り替え、新規に作成されたバッファの削除に使うことのできるハンドルを返します。 
 
YY_BUF_SIZE  デフォルトのバッファ・サイズを定義するマクロです。yy_create_buffer() に渡すべきサイズが分からない場合に、これを使うことができます。 
 
void yy_switch_to_buffer(YY_BUFFER_STATE new_buffer バッファを切り替えます。次に読み込まれるトークンは new_buffer で指定されるバッファから取られます。ファイルの終端(EOF)に達するか、次に yy_switch_to_buffer() が呼び出されるまで、new_buffer からトークンが読み込まれます。new_buffer が EOF に達すると、新しいバッファに切り替えることができます。 
 
void yy_delete_buffer(YY_BUFFER_STATE buffer buffer で指定されるバッファを削除し、それに割り当てられたメモリを解放します。 
 
YY_CURRENT_BUFFER  使用中のカレントなバッファを返すマクロです。 
 
これらが、複数の入力バッファを取り扱うのに必要なすべての機能を提供しています。

5.5.2 複数バッファを使う実例

複数のバッファを使うというアイデアを理解するための手助けとして、インクルードすべきファイルを探す C のスキャナの一部を以下に示します。これは C の #include のうち引用符で囲まれた文字列のみを受け付けます。例えば、

は、最後の例のファイル名が空白を含むことになりますが、正当な入力です。ここでの例はまた、EOF ルールとスタート状態の使用法を実演する良い例でもあります。 スタート状態を使ってファイル名のスキャナを生成する方法やバッファの切り替えを発生させる方法に注目してください。ほかに注目すべき重要な点は、<<EOF>> を取り扱うセクション、および、古いバッファに復帰する際に BEGIN を使って確実に正しい状態に遷移するようにする点です。これを怠ると、状態は INITIAL にリセットされ、#include の最後の " が ECHO されてしまいます。

注:<<EOF>> 機能は次の節で説明されます。<<EOF>> が何であり、何を行うものであるかについてのより詳細な議論については、***ページの 3.8 節「スタート状態」を参照してください。

5.6 ファイルの終端(End-Of-File)ルール

ファイルの終端(EOF)が見つかると Flex は yywrap() を呼び、ほかに処理できる状態のファイルが存在するか調べます。yywrap() が 0 以外の値を返すと、もうこれ以上ファイルはないということを示し、したがって、これがまさに入力の最後であるということになります。状況によっては、この時点でさらに処理を行う必要のある場合があります(例えば、入力のために別のファイルをセットアップしたいということがあるかもしれません)。このような場合のために Flex は <<EOF>> 演算子を提供しています。これを使うことで、EOF が見つかった時に実行すべきことを定義することができます。***ページの 5.5.2 節「複数バッファを使う実例」を参照してください。そこには、EOF ルールを使って、終わりのないコメントやインクルードされているファイルの終端を見つける、良い例が示されています。

<<EOF>> 演算子の使用にはいくつか制限があります。それらを以下に示します。


Copyright (C) 1992, 1993 Free Software Foundation

Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.

Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.

Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation.


日本語訳:市川和久
Japanese translation by Kazuhisa Ichikawa (ki@home.email.ne.jp)